package classfile

import (
	"math"
)

const (
	CONSTANT_Utf8               = 1
	CONSTANT_Integer            = 3
	CONSTANT_Float              = 4
	CONSTANT_Long               = 5
	CONSTANT_Double             = 6
	CONSTANT_Class              = 7
	CONSTANT_String             = 8
	CONSTANT_Fieldref           = 9
	CONSTANT_Methodref          = 10
	CONSTANT_InterfaceMethodref = 11
	CONSTANT_NameAndType        = 12
	CONSTANT_MethodHandle       = 15
	CONSTANT_MethodType         = 16
	CONSTANT_InvokeDynamic      = 18
)

type ConstantPool []ConstantInfo

type ConstantInfo interface {
	readInfo(reader *ClassReader)
}

func (self ConstantPool) getConstantInfo(index uint16) ConstantInfo {
	if cpInfo := self[index]; cpInfo != nil {
		return cpInfo
	}
	panic("Invalid constant pool index!")
}

func (self ConstantPool) getNameAndType(index uint16) (string, string) {
	ntInfo := self.getConstantInfo(index).(*ConstantNameAndTypeInfo)
	name := self.getUtf8(ntInfo.nameIndex)
	_type := self.getUtf8(ntInfo.descriptorIndex)
	return name, _type
}

func (self ConstantPool) getClassName(index uint16) string {
	c := self.getConstantInfo(index).(*ConstantClassInfo)
	return c.ClassName()
}

func (self ConstantPool) getUtf8(index uint16) string {
	utf8Info := self.getConstantInfo(index).(*ConstantUtf8Info)
	return utf8Info.str
}

type ConstantClassInfo struct {
	__constantPool__ ConstantPool
	tag              uint8
	nameIndex        uint16
}

func (self *ConstantClassInfo) ClassName() string {
	return self.__constantPool__.getUtf8(self.nameIndex)
}

func (self *ConstantClassInfo) readInfo(reader *ClassReader) {
	self.nameIndex = reader.readUint16()
}

type ConstantMemberrefInfo struct {
	__constantPool__ ConstantPool
	tag              uint8
	classIndex       uint16
	nameAndTypeIndex uint16
}

func (self *ConstantMemberrefInfo) ClassName() string {
	return self.__constantPool__.getClassName(self.classIndex)
}

func (self *ConstantMemberrefInfo) NameAndDescriptor() (string, string) {
	return self.__constantPool__.getNameAndType(self.nameAndTypeIndex)

}

type ConstantFieldrefInfo struct {
	ConstantMemberrefInfo
}

func (self *ConstantFieldrefInfo) readInfo(reader *ClassReader) {
	self.classIndex = reader.readUint16()
	self.nameAndTypeIndex = reader.readUint16()
}

type ConstantMethodrefInfo struct {
	ConstantMemberrefInfo
}

func (self *ConstantMethodrefInfo) readInfo(reader *ClassReader) {
	self.classIndex = reader.readUint16()
	self.nameAndTypeIndex = reader.readUint16()
}

type ConstantInterfaceMethodrefInfo struct {
	ConstantMemberrefInfo
}

func (self *ConstantInterfaceMethodrefInfo) readInfo(reader *ClassReader) {
	self.classIndex = reader.readUint16()
	self.nameAndTypeIndex = reader.readUint16()
}

type ConstantStringInfo struct {
	__constantPool__ ConstantPool

	tag         uint8
	stringIndex uint16
}

func (self *ConstantStringInfo) readInfo(reader *ClassReader) {
	self.stringIndex = reader.readUint16()
}

func (self *ConstantStringInfo) String() string {
	return self.__constantPool__.getUtf8(self.stringIndex)
}

type ConstantIntegerInfo struct {
	__constantPool__ ConstantPool

	tag uint8
	val int32
}

func (self *ConstantIntegerInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint32()
	self.val = int32(bytes)
}

func (self *ConstantIntegerInfo) Value() int32 {
	return self.val
}

type ConstantFloatInfo struct {
	__constantPool__ ConstantPool

	tag uint8
	val float32
}

func (self *ConstantFloatInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint32()
	self.val = math.Float32frombits(bytes)
}

func (self *ConstantFloatInfo) Value() float32 {
	return self.val
}

type ConstantLongInfo struct {
	__constantPool__ ConstantPool

	tag uint8
	val int64
}

func (self *ConstantLongInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint64()
	self.val = int64(bytes)
}

func (self *ConstantLongInfo) Value() int64 {
	return self.val
}

type ConstantDoubleInfo struct {
	__constantPool__ ConstantPool

	tag uint8
	val float64
}

func (self *ConstantDoubleInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint64()
	self.val = math.Float64frombits(bytes)
}

func (self *ConstantDoubleInfo) Value() float64 {
	return self.val
}

type ConstantNameAndTypeInfo struct {
	__constantPool__ ConstantPool

	tag             uint8
	nameIndex       uint16
	descriptorIndex uint16
}

func (self *ConstantNameAndTypeInfo) readInfo(reader *ClassReader) {
	self.nameIndex = reader.readUint16()
	self.descriptorIndex = reader.readUint16()
}

type ConstantUtf8Info struct {
	__constantPool__ ConstantPool

	tag    uint8
	length uint16
	str    string
}

func (self *ConstantUtf8Info) readInfo(reader *ClassReader) {
	self.length = reader.readUint16()
	length := uint32(self.length)
	bytes := reader.readBytes(length)
	self.str = string(bytes)
}

func (self *ConstantUtf8Info) String() string {
	return self.str
}

type ConstantMethodHandleInfo struct {
	__constantPool__ ConstantPool

	tag            uint8
	referenceKind  uint8
	referenceIndex uint16
}

func (self *ConstantMethodHandleInfo) readInfo(reader *ClassReader) {
	self.referenceKind = reader.readUint8()
	self.referenceIndex = reader.readUint16()
}

type ConstantMethodTypeInfo struct {
	__constantPool__ ConstantPool

	tag             uint8
	descriptorIndex uint16
}

func (self *ConstantMethodTypeInfo) readInfo(reader *ClassReader) {
	self.descriptorIndex = reader.readUint16()
}

type ConstantInvokeDynamicInfo struct {
	__constantPool__ ConstantPool

	tag                      uint8
	bootstrapMethodAttrIndex uint16
	nameAndTypeIndex         uint16
}

func (self *ConstantInvokeDynamicInfo) readInfo(reader *ClassReader) {
	self.bootstrapMethodAttrIndex = reader.readUint16()
	self.nameAndTypeIndex = reader.readUint16()
}

func readConstantPool(reader *ClassReader, constantLength uint16) ConstantPool {
	cp := make(ConstantPool, constantLength)
	count := int(constantLength)
	for i := 1; i < count; i++ {
		cp[i] = readConstantInfo(reader, cp)
		//fmt.Printf("%d %T\n", i, cp[i])
		switch cp[i].(type) {
		case *ConstantLongInfo, *ConstantDoubleInfo:
			i++
		}
	}
	return cp
}

func readConstantInfo(reader *ClassReader, cp ConstantPool) ConstantInfo {
	tag := reader.readUint8()
	c := newConstantInfo(tag, cp)
	c.readInfo(reader)
	return c
}

func newConstantInfo(tag uint8, cp ConstantPool) ConstantInfo {
	switch tag {
	case CONSTANT_Class:
		return &ConstantClassInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_Fieldref:
		return &ConstantFieldrefInfo{ConstantMemberrefInfo{tag: tag, __constantPool__: cp}}
	case CONSTANT_Methodref:
		return &ConstantMethodrefInfo{ConstantMemberrefInfo{tag: tag, __constantPool__: cp}}
	case CONSTANT_InterfaceMethodref:
		return &ConstantInterfaceMethodrefInfo{ConstantMemberrefInfo{tag: tag, __constantPool__: cp}}
	case CONSTANT_String:
		return &ConstantStringInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_Integer:
		return &ConstantIntegerInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_Float:
		return &ConstantFloatInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_Long:
		return &ConstantLongInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_Double:
		return &ConstantDoubleInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_NameAndType:
		return &ConstantNameAndTypeInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_Utf8:
		return &ConstantUtf8Info{tag: tag, __constantPool__: cp}
	case CONSTANT_MethodHandle:
		return &ConstantMethodHandleInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_MethodType:
		return &ConstantMethodTypeInfo{tag: tag, __constantPool__: cp}
	case CONSTANT_InvokeDynamic:
		return &ConstantInvokeDynamicInfo{tag: tag, __constantPool__: cp}
	default:
		panic("java.lang.ClassFormatError: constant pool tag!")
	}
}
